package com.dapangdong.testdemo.aspect;

import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.dapangdong.testdemo.annotation.Operation;
import com.dapangdong.testdemo.utils.ReactiveAdderUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;

/**
 * <p>
 *
 * </p>
 *
 * @author : yhd
 * @program: gulimall
 * @since : 2023-04-12 15:16
 */
@Slf4j
@Aspect     //我是一个AOP切面类
@Component
public class OperationAspect {


    /**
     * 定义一个切入点表达式,用来确定哪些类需要代理
     * execution(* aopdemo.*.*(..))代表aopdemo包下所有类的所有方法都会被代理
     */
    @Pointcut("@annotation(com.dapangdong.testdemo.annotation.Operation)")
    public void pointCut() {
    }

    /**
     * 前置方法,在目标方法执行前执行
     *
     * @param joinPoint 封装了代理方法信息的对象,若用不到则可以忽略不写
     */
    @Before("pointCut()")
    public void beforeMethod(JoinPoint joinPoint) {
        log.info("=====================【前置方法】=====================");
        HttpServletRequest request = getHttpServletRequest();
        //请求ip
        String adder = ReactiveAdderUtil.getIpAdder(request);

        /*
          当我们在浏览器地址栏上输入：locahost:8080/Test/test.html?name=kiku
          request 对象通过以下方法来获取请求路径：
          getServerName()：获取服务器名，localhost，返回对象是 String；
          getServerPort()：获取服务器端口号，8080，返回对象是 int；
          getContextPath()：获取项目名，/Test，返回对象是 String；
          getServletPath()：获取Servlet路径，/test.html，返回对象是 String；
          getQueryString()：获取参数部分，即问号后面的部分：name=zhang，返回对象是 String；
          getRequestURI()：获取请求URI，等于项目名+Servlet路径：/Test/test.html，返回对象是 String；
          getRequestURL()：获取请求URL，等于不包含参数的整个请求路径：localhost:8080/Test/test.html，返回对象是 StringBuffer；
          request.getRemoteAddr()：获取服务器的 IP，localhost 对应 IP 为 127.0.0.1，返回对象是 String；
         */
        String requestedSessionId = request.getRequestedSessionId();

        //获取Web项目的全路径
        String strDirPath = request.getSession().getServletContext().getRealPath("/");
        //请求方式
        String requestMethod = request.getMethod();

        //这个方法能获得url后面的参数串
        String queryString = request.getQueryString();
        //接口绝对路径
        StringBuffer requestURL = request.getRequestURL();
        //接口路径
        String requestURI = request.getRequestURI();

        Signature signature = joinPoint.getSignature();
        //类路径
        String className = signature.getDeclaringTypeName();
        //方法名
        String methodName = signature.getName();
        Method[] methods = signature.getDeclaringType().getMethods();
        //通过反射获取示例对象
        Class<?> targetClass = joinPoint.getTarget().getClass();
        //传参
        String params = getServiceMethodParams(joinPoint);

        MethodSignature methodSignature = (MethodSignature) signature;
        // 被切的方法
        Method method = methodSignature.getMethod();
        //自定义注解-参数
        String description = "";
        Operation annotation = method.getAnnotation(Operation.class);
        if (annotation != null) {
            //注解上的描述
            description = annotation.description();
        }


        // 返回类型
        Class<?> methodReturnType = method.getReturnType();
        // 实例化
//        Object o = methodReturnType.newInstance();

        System.out.println("methods:" + Arrays.toString(methods));

        log.info("requestedSessionId:" + requestedSessionId);
        log.info("请求ip:" + adder);
        log.info("请求方式:" + requestMethod);
        log.info("请求地址:" + requestURL + (StrUtil.isNotEmpty(queryString) ? "?" : "") + Optional.ofNullable(queryString).orElse(""));
        log.info("接口路径:" + requestURI);
        log.info("自定义注解-参数:" + description);
        log.info("方法路径:" + className + "." + methodName);
        log.info("目标对象类型:" + targetClass);
        log.info("参数params:" + params);
    }

    /**
     * 后置方法,在目标方法执行后执行
     *
     * @param joinPoint 封装了代理方法信息的对象,若用不到则可以忽略不写
     */
    @AfterReturning("pointCut()")
    public void afterMethod(JoinPoint joinPoint) {
        log.info("=====================【后置方法】=====================");
        Method[] methods = joinPoint.getSignature().getDeclaringType().getMethods();
//        System.out.println("methods:" + Arrays.toString(methods));

    }

    /**
     * 环绕方法,可自定义目标方法执行的时机
     *
     * @param pjd JoinPoint的子接口,添加了
     *            Object proceed() throws Throwable 执行目标方法
     *            Object proceed(Object[] var1) throws Throwable 传入的新的参数去执行目标方法
     *            两个方法
     * @return 此方法需要返回值, 返回值视为目标方法的返回值
     */
    @Around("pointCut()")
    public Object aroundMethod(ProceedingJoinPoint pjd) {
        long startTime = System.currentTimeMillis();
        log.info("=====================【环绕方法】=====================");
        // 定义返回对象、得到方法需要的参数
        Object result = null;
        Object[] args = pjd.getArgs();
        try {
            //前置通知
            System.out.println("目标方法执行前...");
            //执行目标方法
            result = pjd.proceed(args);
            //返回通知
            log.info("目标方法返回结果后...");
        } catch (Throwable e) {
            //异常通知
            log.error("执行目标方法异常后...");
//            throw new RuntimeException(e);
        }

        //后置通知
        log.info("目标方法执行后...");


        String methodName = pjd.getSignature().getName();
        long endTime = System.currentTimeMillis();
        // 打印耗时的信息
        this.printExecTime(methodName, startTime, endTime);
        return result;
    }


    /**
     * 打印方法执行耗时的信息，如果超过了一定的时间，才打印
     *
     * @param methodName 方法名
     * @param startTime  开始时间
     * @param endTime    结束时间
     */
    private void printExecTime(String methodName, long startTime, long endTime) {
        long diffTime = endTime - startTime;
        log.warn("【" + methodName + "】方法执行耗时：" + diffTime + "毫秒");
    }

    /**
     * 获取json格式的参数用于存储到数据库中
     *
     * @param joinPoint 切入点
     * @return json
     */
    private String getServiceMethodParams(JoinPoint joinPoint) {
        Object[] arguments = joinPoint.getArgs();
        log.info("参数:" + Arrays.toString(arguments));
        return JSONUtil.toJsonStr(joinPoint.getArgs());
    }

    /**
     * 获取当前的request
     * 这里如果报空指针异常是因为单独使用spring获取request
     * 需要在配置文件里添加监听
     * <listener>
     * <listener-class>
     * org.springframework.web.context.request.RequestContextListener
     * </listener-class>
     * </listener>
     *
     * @return HttpServletRequest
     */
    public HttpServletRequest getHttpServletRequest() {
//        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
//        ServletRequestAttributes sra = (ServletRequestAttributes)ra;
        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
    }


}


